package com.naiterui.ehp.bs.patient.service.impl;

import cn.hutool.core.map.MapUtil;
import cn.hutool.json.JSONUtil;
import com.naiterui.common.redis.RedisUtil;
import com.naiterui.ehp.bp.bo.consult.VideoConsultBO;
import com.naiterui.ehp.bp.bo.consult.VideoConsultMessageBO;
import com.naiterui.ehp.bp.bo.patient.MsgMaPublishParamBO;
import com.naiterui.ehp.bp.bo.payment.NotifyBusinessRefundBO;
import com.naiterui.ehp.bp.bo.smh.SmhUserBO;
import com.naiterui.ehp.bp.constants.CommonConstant;
import com.naiterui.ehp.bp.constants.OrderSnType;
import com.naiterui.ehp.bp.constants.PayStatus;
import com.naiterui.ehp.bp.domain.Doctor;
import com.naiterui.ehp.bp.domain.DrChargeConfig;
import com.naiterui.ehp.bp.domain.DrVideoConsult;
import com.naiterui.ehp.bp.domain.Patient;
import com.naiterui.ehp.bp.domain.PatientInquirerInfo;
import com.naiterui.ehp.bp.support.exception.BaseExceptionCodes;
import com.naiterui.ehp.bp.support.exception.BusinessException;
import com.naiterui.ehp.bp.utils.biz.AppJumpProtocol;
import com.naiterui.ehp.bp.utils.biz.ScheduleUtils;
import com.naiterui.ehp.bp.utils.date.DateUtil;
import com.naiterui.ehp.bp.utils.lang.ConfigUtil;
import com.naiterui.ehp.bp.utils.notice.AppNoticeUtil;
import com.naiterui.ehp.bp.utils.notice.AppPushUtil;
import com.naiterui.ehp.bp.utils.payment.OrderUtils;
import com.naiterui.ehp.bp.utils.payment.bo.UnifiedOrderRequestBO;
import com.naiterui.ehp.bp.utils.payment.bo.UnifiedOrderResultBO;
import com.naiterui.ehp.bp.utils.thread.ThreadPoolUtil;
import com.naiterui.ehp.bs.doctor.service.IDrFollowUpRecordService;
import com.naiterui.ehp.bs.patient.constants.CacheConstant;
import com.naiterui.ehp.bs.patient.dao.IDoctorDao;
import com.naiterui.ehp.bs.patient.dao.IDrMessageConsultChargeDao;
import com.naiterui.ehp.bs.patient.dao.IPatientDao;
import com.naiterui.ehp.bs.patient.domain.WeixinInfo;
import com.naiterui.ehp.bs.patient.feign.EmrFeginClient;
import com.naiterui.ehp.bs.patient.feign.VideoConsultFeignClient;
import com.naiterui.ehp.bs.patient.repository.IPatientInquirerRepository;
import com.naiterui.ehp.bs.patient.repository.IWeixinInfoRepository;
import com.naiterui.ehp.bs.patient.service.IVideoConsultService;
import com.naiterui.ehp.bs.patient.service.function.IFunctionService;
import com.naiterui.ehp.bs.patient.service.message.IMessageSendService;
import com.naiterui.ehp.bs.patient.utils.ConfigKeyUtil;
import com.naiterui.ehp.bs.patient.vo.AddVideoConsultVO;
import com.naiterui.ehp.bs.wechat.service.ICustomMsgApiService;
import com.naiterui.ehp.bs.wechat.service.IWebAuthorizeApiService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author luao
 * @date 2021/2/26 11:06 上午
 **/
@Slf4j
@Service
@RequiredArgsConstructor
public class ApVideoConsultServiceImpl implements IVideoConsultService {

    /**
     * 患者域名
     */
    private static final String PATIENT_URL = ConfigKeyUtil.getString("patient.url");
    /**
     * 视频咨询域名
     */
    private static final String VIDEO_CONSULT_REFUND_CALLBACK = ConfigKeyUtil.getString("video.consult.refund.callback");
    private static String TASK_SCHEDULE_HOST = ConfigUtil.getString("schedule.host.url");
    /**
     * 患者联系客服地址
     */
    private final String patientCustomerServiceUrl = ConfigUtil.getString("patient.customer.service.url");

    private final IDrMessageConsultChargeDao drMessageConsultChargeDao;
    private final IPatientDao patientDao;
    private final IDoctorDao doctorDao;
    private final IPatientInquirerRepository inquirerRepository;

    private final IFunctionService functionService;
    private final ICustomMsgApiService customMsgApiService;
    private final IWebAuthorizeApiService webAuthorizeApiServiceImpl;

    private final VideoConsultFeignClient videoConsultFeignClient;
    private final IWeixinInfoRepository weixinInfoRepository;
    private final IMessageSendService messageSendService;
    private final EmrFeginClient emrFeginClient;
    private final IDrFollowUpRecordService drFollowUpRecordService;

    @Override
    public Integer startVideo(Long patientId, Long doctorId) throws BusinessException {
        Patient patient = patientDao.get(patientId);
        Doctor doctor = doctorDao.get(doctorId);
        return startVideo(patient, doctor, true);
    }

    public Integer startVideo(Patient patient, Doctor doctor, boolean needAuth) throws BusinessException {
        if (doctor == null || patient == null) {
            log.error("startChat ERROR ; doctor or patient info not exist : patient={},doctor={},needAuth={}", patient, doctor, needAuth);
            return CacheConstant.MESSAGE_HANDLE_NORMAL;
        }
        Long patientId = patient.getId();
        Long doctorId = doctor.getId();

        if (!needAuth) {
            //若无需认证等客服消息提示，忽略校验及相关推送
            log.info("startChat INFO ; do not needAuth : patient={},doctor={}", patient, doctor);
            return CacheConstant.MESSAGE_HANDLE_NORMAL;
        }

        /*if (Patient.REAL_STATUS_SUCCESS != patient.getStatus()) {
            //患者未认证：客服消息——>您还未完善个人信息，点击完善(个人信息认证链接)
            customMsgApiService.sendSystemMsg(patient.getOpenId(), "感谢您信任并使用${ehp_hospital_hotline}，线上问诊需实名认证，否则无法咨询医生，感谢您的配合。\n----------\n" +
                    webAuthorizeApiServiceImpl.getWebAuthorizeUrlSilent(ConfigUtil.getString("wechat.redirect.common.url"),
                            PatientJumperEnum.PATIENT_AUTH.encodeWxParamStr(), "点击完成认证"));
            return CacheConstant.MESSAGE_HANDLE_NORMAL;
        }*/

        // 验证就诊人是否可以发起视频问诊
        // List<PatientInquirerInfo> inquirerInfoList =
        // inquirerRepository.findByPatientIdAndStatus(patientId, PatientInquirerInfo.STATUS_DEFAULT);
        // List<String> inquirerIdCards = inquirerInfoList.stream().filter(i -> StringUtils.isNotBlank(i.getIdCard()))
        // .map(PatientInquirerInfo::getIdCard).collect(Collectors.toList());
        // if (CollectionUtils.isEmpty(inquirerIdCards)) {
        // throw new BusinessException(ExceptionCodes.INQUIRER_NOT_HIS_RECORD);
        // }
        // List<SmhUserBO> patientRecords = emrFeginClient.getPatientRecords(inquirerIdCards);
        // if (CollectionUtils.isEmpty(patientRecords)) {
        // log.info("患者无线下就诊记录，不可发起视频问诊 patientId:{}, doctorId:{}", patientId, doctorId);
        // throw new BusinessException(ExceptionCodes.INQUIRER_NOT_HIS_RECORD);
        // }

        //正常可咨询，又进行中，关闭页面进入聊天框，否则跳转
        if (RedisUtil.keyOps().existsKey(CacheConstant.getVideoSessionKey(doctorId, patientId))) {
            customMsgApiService.sendSystemMsg(patient.getOpenId(),
                    "您已选择向" + doctor.getName() + "医生咨询，请先完成当前接诊哦~");
            return CacheConstant.MESSAGE_HANDLE_NORMAL;
        }

        return CacheConstant.MESSAGE_HANDLE_REDIRECT;
    }

    @Override
    public UnifiedOrderResultBO addPatientVideoConsultPayInfo(Long patientId, AddVideoConsultVO addVideoConsultVO,
                                                              String ip, Integer origin) throws BusinessException {

        Long doctorId = addVideoConsultVO.getDoctorId();
        Long inquirerId = addVideoConsultVO.getInquirerId();
        // 校验就诊人是否有下线就诊记录
        PatientInquirerInfo inquirer = inquirerRepository.get(inquirerId);
        List<SmhUserBO> patientRecords = emrFeginClient.getPatientRecords(MapUtil.builder(inquirer.getIdCard(), inquirer.getName()).build());
        if (org.apache.commons.collections4.CollectionUtils.isEmpty(patientRecords)) {
            log.info("该就诊人无线下就诊记录，不可发起视频问诊 inquirerId:{}", inquirer);
            throw new BusinessException(BaseExceptionCodes.INQUIRER_NOT_HIS_RECORD);
        }
        // 判断医患关系是否删除
        functionService.isdDleteDoctorPatientRelation(doctorId, patientId);
        // 查询医生的设置的价格
        Integer consultCost = drMessageConsultChargeDao.getChargeById(doctorId, patientId, DrChargeConfig.TYPE_VIDEO);

        // 获取最近一次的视频问诊信息
        VideoConsultBO videoConsultBO = videoConsultFeignClient.getVideoConsult(patientId, doctorId);
        if (videoConsultBO != null) {
            // 如果有进行中的会话，会话无需支付
            notOverVideoConsult(videoConsultBO);
            // 如果该视频支付不是待支付状态，则赋值为空，重新添加视频问诊信息
            if (videoConsultBO.getPayStatus() != PayStatus.WAIT_PAY.getValue()) {
                videoConsultBO = null;
            }
        }
        boolean isSave = false;
        if (videoConsultBO == null) {
            log.info("支付请求视频咨询订单号,生成新的订单,参数: patientId {}, doctorId {}", patientId, doctorId);
            // 设置唯一key，值为支付状态，待支付
            String orderSn = OrderUtils.getOrderSn(OrderSnType.VIDEO_ORDERSN);
            videoConsultBO = VideoConsultBO.builder().orderSn(orderSn).doctorId(doctorId).patientId(patientId)
                    .inquirerId(inquirerId).videoConsultStatus(DrVideoConsult.VIDEO_CONSULT_STATUS_NO_ORDER)
                    .payStatus(PayStatus.WAIT_PAY.getValue()).conditionDesc(addVideoConsultVO.getConditionDesc()).offlineDiagnosis(addVideoConsultVO.getOfflineDiagnosis()).build();
            isSave = true;
        }
        if (0 == consultCost) {
            videoConsultBO.setVideoConsultStatus(DrVideoConsult.VIDEO_CONSULT_STATUS_TO_TREAT);
            videoConsultBO.setPayStatus(PayStatus.PAY_SUCCESS.getValue());
            videoConsultBO.setConsultTime(new Date());
        }
        // 添加或者更新视频咨询信息
        videoConsultBO.setPrice(consultCost);
        videoConsultBO = videoConsultFeignClient.saveOrUpdate(videoConsultBO);
        // 如果是新添加的视频咨询信息，则需要添加视频咨询消息
        // if (isSave) {
        // this.saveVideoConsultMessage(doctorId, patientId, inquirerId, videoConsultBO.getId());
        // }
        String orderSn = videoConsultBO.getOrderSn();
        // 如果是零元订单。直接返回即可
        if (0 == consultCost) {
            // 支付成功
            paySuccess(videoConsultBO.getPatientId(), videoConsultBO.getDoctorId(), videoConsultBO.getId(), orderSn, videoConsultBO.getRoomId());
            return UnifiedOrderResultBO.builder()
                    .orderSn(orderSn).payStatus(PayStatus.PAY_SUCCESS.getValue()).build();
        }
        Patient patient = patientDao.get(patientId);
        WeixinInfo weixinInfo = weixinInfoRepository.findByMiniOpenId(patient.getMiniOpenId());
        // 调用微信支付
        return OrderUtils.pay(UnifiedOrderRequestBO.builder()
                // 订单号
                .orderSn(orderSn)
                // 商品名称
                .body(orderSn)
                // 用户OPEN id
                .openId(weixinInfo.getMiniOpenId())
                // 用户ip
                .ip(ip)
                // 订单金额
                .totalFee(consultCost)
                // 订单来源
                .origin(origin)
                // 业务通知地址
                .notifyBusinessUrl(PATIENT_URL + "/inner/notify/pay")
                // 订单类型
                .orderType(OrderSnType.VIDEO_ORDERSN).build());
    }

    public void notOverVideoConsult(VideoConsultBO videoConsultBO) throws BusinessException {
        if (videoConsultBO.getPayStatus() != PayStatus.WAIT_PAY.getValue()) {
            Integer videoConsultStatus = videoConsultBO.getVideoConsultStatus();
            /*if (videoConsultStatus != DrVideoConsult.VIDEO_CONSULT_STATUS_CANCEL && videoConsultStatus != DrVideoConsult.VIDEO_CONSULT_STATUS_COMPLETE) {
                throw new BusinessException(ExceptionCodes.SESSION_STATUS_NO_PAY);
            }*/
        }
    }

    /**
     * 添加视频咨询消息
     *
     * @param doctorId
     * @param patientId
     * @param inquirerId
     * @param videoConsultId
     */
    public void saveVideoConsultMessage(Long doctorId, Long patientId, Long inquirerId, Long videoConsultId) {
        // 关联病情病例信息
        String prepareValue = RedisUtil.hashOps().hget(
                CacheConstant.getSessionPrepareKey(), CacheConstant.getVideoSessionKey(doctorId, patientId));

        Integer type = 0;
        String recordId = null;
        if (StringUtils.isNotBlank(prepareValue)) {
            String[] prepareParam = prepareValue.split("_");
            type = Integer.parseInt(prepareParam[0]);
            recordId = prepareParam[1];
        }
        Integer messageType;
        switch (type) {
            case 1:
                messageType = 4;
                break;
            case 2:
                messageType = 3;
                break;
            default:
                messageType = 0;
                break;
        }
        // 添加视频咨询消息
        videoConsultFeignClient.saveVideoConsultMessage(VideoConsultMessageBO.builder()
                .content(recordId)
                .doctorId(doctorId)
                .patientId(patientId)
                .inquirerId(inquirerId)
                .type(messageType)
                .videoConsultId(videoConsultId)
                .build());
    }

    @Override
    public void payNotify(String orderSn, Integer payStatus) {
        log.info("视频咨询支付回调 orderSn:{}, payStatus:{}", orderSn, payStatus);
        // 根据订单号查询
        VideoConsultBO videoConsultBO = videoConsultFeignClient.getVideoConsultByOrderSn(orderSn);
        if (videoConsultBO == null) {
            log.error("视频咨询支付回调--根据订单号查询失败  orderSn:{}", orderSn);
            return;
        }
        // 修改订单状态
        videoConsultFeignClient.saveOrUpdate(
                VideoConsultBO.builder()
                        .id(videoConsultBO.getId())
                        .payStatus(payStatus)
                        .videoConsultStatus(DrVideoConsult.VIDEO_CONSULT_STATUS_TO_TREAT)
                        .consultTime(new Date())
                        .build()
        );
        if (PayStatus.PAY_SUCCESS.getValue() != payStatus) {
            return;
        }
        // 支付成功
        paySuccess(videoConsultBO.getPatientId(), videoConsultBO.getDoctorId(), videoConsultBO.getId(), orderSn, videoConsultBO.getRoomId());
    }

    /**
     * 支付成功调用
     *
     * @param patientId
     * @param doctorId
     * @param videoConsultId
     * @param orderSn
     */
    public void paySuccess(Long patientId, Long doctorId, Long videoConsultId, String orderSn, Integer roomId) {
        Patient patient = patientDao.get(patientId);
        Doctor doctor = doctorDao.get(doctorId);

        // 添加定时任务，60分钟后无人接诊自动取消
        Date date = DateUtil.addMinutes(new Date(), CommonConstant.VIDEO_CONSULT_AUTO_CANCEL_TIME);
        String invokeTime = DateUtil.formatDate(date, DateUtil.CRON_TIME_FORMAT);
        Map paramMap = MapUtil.newHashMap();
        paramMap.put("videoConsultId", videoConsultId);
        try {
            log.info("视频咨询无人接诊自动取消定时开始，invokeTime:{} callback:{}", invokeTime, VIDEO_CONSULT_REFUND_CALLBACK);
            ScheduleUtils.addCustomerTask(TASK_SCHEDULE_HOST, invokeTime, ScheduleUtils.TASH_TYPE_VIDEO_CONSULT, orderSn,
                    VIDEO_CONSULT_REFUND_CALLBACK, paramMap);
        } catch (BusinessException e) {
            log.error("添加六十分钟无人接诊自动取消失败 errMsg:{}", e);
        }

        // 支付成功后，缓存当前医生患者代表正在咨询中的状态
        RedisUtil.valueOps().set(CacheConstant.getVideoSessionKey(doctorId, patientId), videoConsultId);
        // 给医生端发送待接诊消息
        AppNoticeUtil.VideoConsult noticeInfo = new AppNoticeUtil.VideoConsult(AppNoticeUtil.VideoConsult.OPERATE_ADD);
        // amp服务地址
        String ampHost = ConfigUtil.getString("amp.host.url");
        try {
            AppNoticeUtil.sendSingleNotice(ampHost, doctor.getId(), AppNoticeUtil.NoticeType.DR_VIDEO_CONSULT, noticeInfo);
        } catch (BusinessException e) {
            log.error("发送医生视频咨询消息失败", e);
        }

        // 给医生推送push消息
        try {
            AppPushUtil.sendDrPush(ConfigUtil.getString("push.message.server"), doctorId, "您收到一个新的视频申请，请及时确认", AppJumpProtocol.DR_CONSULT_VIDEO_LIST, "");
        } catch (BusinessException e) {
            log.error("视频问诊医生推送push消息异常， {}", e);
        }


        // 发送患者消息
        // customMsgApiService.sendSystemMsg(patient.getOpenId(),
        // "您已成功提交" + doctor.getName() + "的视频问诊申请，请您耐心等待医生接诊~~\r\n------------------------------" +
        // "\r\n<a
        // href='http://patient.netrain.naiterui.com/work/html/onlineService.html#online_service%2C%7B%7D'>【联系客服】</a>");
        // customMsgApiService.sendSystemMsg(patient.getOpenId(),
        // "您已成功提交" + doctor.getName() + "医生线上视频复诊预约，请耐心等待医生接诊，接诊后系统会及时通知，请关注服务号订阅通知，并按照医生设置时间准时候诊，感谢您的理解和配合~");
        handleVisit(doctorId, patientId, videoConsultId, roomId);
    }

    private void handleVisit(Long doctorId, Long patientId, Long videoConsultId, Integer roomId) {
        ThreadPoolUtil.execute(() -> {
            Date consultTime = new Date();
            try {
                drFollowUpRecordService.updateVisitCompleteStatus(doctorId, patientId, consultTime, CommonConstant.ConsultType.VIDEO, String.valueOf(roomId), videoConsultId);
            } catch (BusinessException e) {
                log.error("视频问诊处理随访任务异常：{}", e);
            }
        });
    }

    @Override
    public void refundNotify(String orderSn, Integer refundStatus) {
        log.info("视频咨询退款支付回调 orderSn:{}, refundStatus:{}", orderSn, refundStatus);
        if (NotifyBusinessRefundBO.REFUND_SUCCESS != refundStatus) {
            log.info("退款失败 orderSn:{}, refundStatus", orderSn, refundStatus);
            return;
        }
        // 根据订单号查询
        VideoConsultBO videoConsultBO = videoConsultFeignClient.getVideoConsultByOrderSn(orderSn);
        if (videoConsultBO == null || videoConsultBO.getPayStatus() != PayStatus.PAY_SUCCESS.getValue()) {
            log.error("没有查到视频咨询订单或者订单状态不是支付成功  orderSn:{}", orderSn);
            return;
        }
        // 发送小程序订阅消息
        Map<String, Object> paramsMap = new HashMap<>(6);
        paramsMap.put("orderNumber", orderSn);
        paramsMap.put("productName", "视频问诊");
        paramsMap.put("refundAmount", videoConsultBO.getPrice() / 100);
        paramsMap.put("refundCause", "预约取消");
        messageSendService.publishMaMessage(MsgMaPublishParamBO.builder()
                .doctorId(videoConsultBO.getDoctorId())
                .patientId(videoConsultBO.getPatientId())
                .msgType(MsgMaPublishParamBO.MsgType.REFUND_NOTICE)
                .content(JSONUtil.toJsonStr(paramsMap))
                .build());
        // 修改订单状态
        videoConsultFeignClient.saveOrUpdate(
                VideoConsultBO.builder()
                        .id(videoConsultBO.getId())
                        .payStatus(PayStatus.REFUND.getValue())
                        .videoConsultStatus(DrVideoConsult.VIDEO_CONSULT_STATUS_CANCEL)
                        .isTreat(0)
                        .build()
        );
    }
}
